# ble_beacon_scanner.py - ESP32-S3蓝牙信标扫描插件（稳定优化版）
import bluetooth
import uasyncio as asyncio
import time
import gc
from micropython import const

# 扫描事件常量
_IRQ_SCAN_RESULT = const(5)
_IRQ_SCAN_DONE = const(6)

class BLEBeaconScanner:
    """
    ESP32-S3蓝牙信标扫描器插件 - 稳定优化版
    支持大量设备扫描而不崩溃
    """
    
    def __init__(self, loader):
        self.loader = loader
        self.ble = None
        self.scanning = False
        self.running = True
        
        # 设备追踪字典 - 限制最大设备数
        self.esp_c3_devices = {}  # {mac: {name, rssi_history, last_seen, first_seen, online}}
        self.max_devices = 50  # 最多追踪50个设备
        
        # 扫描参数（优化后的快速响应配置）
        self.scan_interval = 2.0  # 2秒扫描间隔
        self.scan_duration = 1300  # 1.3秒扫描时长
        self.history_size = 5  # 减少到5次历史记录（节省内存）
        self.report_interval = 3  # 每3秒上报一次
        self.offline_timeout = 8  # 8秒未见视为离线
        
        # 批处理配置
        self.batch_size = 30  # 每批最多处理30个设备
        self.batch_delay = 0.1  # 批次之间延迟100ms
        
        # 能力声明
        self.capabilities = ["ble_beacon", "rssi_tracking", "multi_beacon"]
        
        # 统计数据
        self.stats = {
            'scans_completed': 0,
            'devices_tracked': 0,
            'reports_sent': 0,
            'total_detections': 0,
            'last_report_time': 0,
            'memory_cleanups': 0,
            'errors_caught': 0
        }
        
        # 任务句柄
        self._scan_task = None
        self._report_task = None
        self._cleanup_task = None
        
        # 内存阈值
        self.memory_threshold = 20000  # 低于20KB触发清理
        self.critical_memory = 10000   # 低于10KB进入紧急模式
        
        # 错误计数器
        self.consecutive_errors = 0
        self.max_consecutive_errors = 5
        
    async def init(self):
        """初始化蓝牙扫描器"""
        try:
            print("[BLE信标] ========== 初始化BLE扫描器 ==========")
            
            # 内存检查
            gc.collect()
            free_mem = gc.mem_free()
            print("[BLE信标] 初始内存: %d bytes" % free_mem)
            
            if free_mem < self.memory_threshold:
                print("[BLE信标] 内存不足，跳过初始化")
                return False
            
            # 激活蓝牙
            self.ble = bluetooth.BLE()
            self.ble.active(True)
            self.ble.irq(self._irq_handler)
            
            print("[BLE信标] 蓝牙模块已激活")
            print("[BLE信标] 最大设备数: %d" % self.max_devices)
            print("[BLE信标] 扫描参数: 间隔=%ds, 时长=%dms" % (self.scan_interval, self.scan_duration))
            
            # 发送启动日志
            await self.loader.send_log('info', 'BLE信标扫描器已启动（安全模式）', {
                'max_devices': self.max_devices,
                'scan_interval': self.scan_interval,
                'memory_free': free_mem
            })
            
            # 启动异步任务
            self._scan_task = asyncio.create_task(self._safe_scan_loop())
            self._report_task = asyncio.create_task(self._safe_report_loop())
            self._cleanup_task = asyncio.create_task(self._memory_cleanup_loop())
            
            print("[BLE信标] ========== 初始化完成 ==========")
            return True
            
        except Exception as e:
            print("[BLE信标] 初始化失败: %s" % e)
            self.stats['errors_caught'] += 1
            try:
                await self.loader.send_log('error', 'BLE信标扫描器初始化失败: %s' % str(e))
            except:
                pass
            return False
    
    def _irq_handler(self, event, data):
        """处理BLE扫描事件（中断处理器）"""
        try:
            if event == _IRQ_SCAN_RESULT:
                # 检查内存
                if gc.mem_free() < self.critical_memory:
                    return
                
                try:
                    # 解析扫描结果
                    addr_type, addr, adv_type, rssi, adv_data = data
                    
                    # 限制设备数量
                    if len(self.esp_c3_devices) >= self.max_devices:
                        addr_str = ':'.join(['%02X' % b for b in addr])
                        if addr_str not in self.esp_c3_devices:
                            return
                    
                    # 转换MAC地址
                    addr_str = ':'.join(['%02X' % b for b in addr])
                    
                    # 解析设备名称
                    name = self._decode_name_safe(adv_data)
                    
                    # 只处理ESP-C3设备
                    if name and name.startswith('ESP-C3'):
                        self._update_device_safe(addr_str, name, rssi)
                        self.stats['total_detections'] += 1
                        
                except Exception as e:
                    self.stats['errors_caught'] += 1
                    
            elif event == _IRQ_SCAN_DONE:
                self.scanning = False
                self.stats['scans_completed'] += 1
                
        except Exception as e:
            self.stats['errors_caught'] += 1
            self.scanning = False
    
    def _decode_name_safe(self, adv_data):
        """安全地从BLE广播数据中解析设备名称"""
        try:
            if not adv_data or len(adv_data) < 3:
                return None
                
            i = 0
            max_iterations = 50
            iterations = 0
            
            while i < len(adv_data) and iterations < max_iterations:
                iterations += 1
                
                try:
                    length = adv_data[i]
                    if length == 0 or i + 1 + length > len(adv_data):
                        break
                    
                    type_byte = adv_data[i + 1]
                    
                    # 0x09 = Complete Local Name, 0x08 = Shortened Local Name
                    if type_byte in (0x08, 0x09):
                        payload = adv_data[i + 2:i + 1 + length]
                        
                        try:
                            return payload.decode('utf-8')
                        except:
                            # 尝试ASCII
                            result = ''
                            for b in payload:
                                if 32 <= b < 127:
                                    result += chr(b)
                            return result if result else None
                    
                    i += 1 + length
                    
                except Exception:
                    break
                    
        except Exception:
            pass
            
        return None
    
    def _update_device_safe(self, mac, name, rssi):
        """安全地更新设备信息"""
        try:
            now = time.time()
            
            # 检查设备数量限制
            if mac not in self.esp_c3_devices:
                if len(self.esp_c3_devices) >= self.max_devices:
                    self._remove_oldest_offline_device()
                    
                if len(self.esp_c3_devices) >= self.max_devices:
                    return
                
                # 新设备
                self.esp_c3_devices[mac] = {
                    'name': name[:20],
                    'rssi_history': [],
                    'last_seen': now,
                    'first_seen': now,
                    'online': True,
                    'update_count': 0
                }
                self.stats['devices_tracked'] += 1
                print("[BLE信标] 新信标: %s [%s] RSSI=%d" % (
                    name[:15], mac[-8:], rssi
                ))
            
            # 更新设备信息
            device = self.esp_c3_devices[mac]
            device['rssi_history'].append(rssi)
            device['last_seen'] = now
            device['online'] = True
            device['update_count'] += 1
            
            # 维护历史记录大小
            if len(device['rssi_history']) > self.history_size:
                device['rssi_history'] = device['rssi_history'][-self.history_size:]
                
        except Exception as e:
            self.stats['errors_caught'] += 1
    
    def _remove_oldest_offline_device(self):
        """移除最旧的离线设备"""
        try:
            offline_devices = [
                (mac, dev) for mac, dev in self.esp_c3_devices.items() 
                if not dev['online']
            ]
            
            if offline_devices:
                # 按last_seen排序，移除最旧的
                offline_devices.sort(key=lambda x: x[1]['last_seen'])
                mac_to_remove = offline_devices[0][0]
                del self.esp_c3_devices[mac_to_remove]
                print("[BLE信标] 移除旧设备: %s" % mac_to_remove[-8:])
                
        except Exception:
            pass
    
    async def _safe_scan_loop(self):
        """安全的异步扫描循环"""
        print("[BLE信标] 扫描任务已启动")
        
        while self.running:
            try:
                # 内存检查
                if gc.mem_free() < self.critical_memory:
                    print("[BLE信标] 内存严重不足，暂停扫描")
                    await asyncio.sleep(5)
                    gc.collect()
                    continue
                
                if not self.scanning:
                    # 启动新扫描
                    self.scanning = True
                    self.consecutive_errors = 0
                    
                    try:
                        self.ble.gap_scan(self.scan_duration, 30000, 30000, False)
                    except Exception as e:
                        self.scanning = False
                        self.stats['errors_caught'] += 1
                        await asyncio.sleep(2)
                        continue
                    
                    # 等待扫描完成
                    scan_start = time.ticks_ms()
                    timeout = self.scan_duration + 500
                    
                    while self.scanning and time.ticks_diff(time.ticks_ms(), scan_start) < timeout:
                        await asyncio.sleep_ms(50)
                    
                    # 强制结束扫描
                    if self.scanning:
                        try:
                            self.ble.gap_scan(None)
                        except:
                            pass
                        self.scanning = False
                    
                    # 检查设备状态
                    self._check_device_status_safe()
                    
                # 扫描间隔
                await asyncio.sleep(self.scan_interval)
                
            except Exception as e:
                self.consecutive_errors += 1
                self.stats['errors_caught'] += 1
                self.scanning = False
                
                # 错误过多，延长等待
                if self.consecutive_errors >= self.max_consecutive_errors:
                    print("[BLE信标] 连续错误过多，延长等待")
                    await asyncio.sleep(10)
                    self.consecutive_errors = 0
                else:
                    await asyncio.sleep(2)
    
    def _check_device_status_safe(self):
        """安全地检查设备在线状态"""
        try:
            now = time.time()
            offline_macs = []
            
            for mac, device in self.esp_c3_devices.items():
                if now - device['last_seen'] > self.offline_timeout:
                    if device['online']:
                        device['online'] = False
                        offline_macs.append(mac)
            
            # 批量打印离线信息
            if offline_macs:
                for mac in offline_macs[:3]:  # 最多打印3个
                    device = self.esp_c3_devices.get(mac)
                    if device:
                        print("[BLE信标] 离线: %s" % mac[-8:])
                        
        except Exception:
            self.stats['errors_caught'] += 1
    
    async def _safe_report_loop(self):
        """安全的定期上报循环"""
        print("[BLE信标] 上报任务已启动")
        
        # 初始延迟
        await asyncio.sleep(3)
        
        while self.running:
            try:
                # 内存检查
                if gc.mem_free() < self.memory_threshold:
                    gc.collect()
                
                # 分批上报
                await self._send_batched_report()
                await asyncio.sleep(self.report_interval)
                
            except Exception as e:
                self.stats['errors_caught'] += 1
                print("[BLE信标] 上报错误")
                await asyncio.sleep(5)
    
    async def _send_batched_report(self):
        """分批发送信标报告"""
        if not self.esp_c3_devices:
            return
        
        try:
            # 获取所有在线设备
            online_devices = [
                (mac, dev) for mac, dev in self.esp_c3_devices.items() 
                if dev['online'] and dev['rssi_history']
            ]
            
            if not online_devices:
                return
            
            # 分批处理
            batch_count = (len(online_devices) + self.batch_size - 1) // self.batch_size
            
            for batch_idx in range(batch_count):
                # 内存检查
                if gc.mem_free() < self.critical_memory:
                    gc.collect()
                    if gc.mem_free() < self.critical_memory:
                        break
                
                # 获取当前批次
                start_idx = batch_idx * self.batch_size
                end_idx = min(start_idx + self.batch_size, len(online_devices))
                batch_devices = online_devices[start_idx:end_idx]
                
                # 构建批次报告
                report_data = {
                    'timestamp': time.time(),
                    'batch': batch_idx + 1,
                    'total_batches': batch_count,
                    'beacons': []
                }
                
                for mac, device in batch_devices:
                    try:
                        rssi_list = device['rssi_history']
                        avg_rssi = sum(rssi_list) / len(rssi_list) if rssi_list else 0
                        
                        beacon_data = {
                            'mac': mac,
                            'name': device['name'],
                            'online': device['online'],
                            'rssi': {
                                'current': rssi_list[-1],
                                'average': round(avg_rssi, 1),
                                'min': min(rssi_list),
                                'max': max(rssi_list),
                                'samples': len(rssi_list)
                            },
                            'last_update': int(time.time() - device['last_seen'])
                        }
                        
                        report_data['beacons'].append(beacon_data)
                        
                    except Exception:
                        self.stats['errors_caught'] += 1
                        continue
                
                # 发送批次数据
                if report_data['beacons']:
                    success = await self.loader.send_data('ble_beacon_batch', report_data)
                    
                    if success:
                        self.stats['reports_sent'] += 1
                        self.stats['last_report_time'] = time.time()
                        
                        # 简化日志输出
                        if batch_idx == 0:
                            print("[BLE信标] 上报: %d个设备 (%d批)" % (
                                len(online_devices), batch_count
                            ))
                    
                    # 批次间延迟
                    if batch_idx < batch_count - 1:
                        await asyncio.sleep(self.batch_delay)
                        
        except Exception as e:
            self.stats['errors_caught'] += 1
            print("[BLE信标] 批次上报失败")
    
    async def _memory_cleanup_loop(self):
        """内存清理循环"""
        print("[BLE信标] 内存监控已启动")
        
        while self.running:
            try:
                await asyncio.sleep(10)  # 每10秒检查一次
                
                free_mem = gc.mem_free()
                
                # 内存不足时的清理策略
                if free_mem < self.memory_threshold:
                    gc.collect()
                    self.stats['memory_cleanups'] += 1
                    
                    # 清理离线设备
                    if free_mem < self.critical_memory:
                        offline_count = 0
                        for mac in list(self.esp_c3_devices.keys()):
                            device = self.esp_c3_devices[mac]
                            if not device['online']:
                                del self.esp_c3_devices[mac]
                                offline_count += 1
                        
                        if offline_count > 0:
                            print("[BLE信标] 清理了 %d 个离线设备" % offline_count)
                    
                    # 缩减历史记录
                    if free_mem < 15000:
                        for device in self.esp_c3_devices.values():
                            if len(device['rssi_history']) > 3:
                                device['rssi_history'] = device['rssi_history'][-3:]
                        
                        print("[BLE信标] 缩减历史记录以节省内存")
                    
                    new_free_mem = gc.mem_free()
                    if new_free_mem > free_mem:
                        print("[BLE信标] 内存清理: %d -> %d bytes" % (
                            free_mem, new_free_mem
                        ))
                        
            except Exception:
                self.stats['errors_caught'] += 1
                await asyncio.sleep(10)
    
    async def handle_command(self, cmd, params):
        """处理来自后端的命令"""
        try:
            if cmd == "ble_status":
                # 返回简化状态
                gc.collect()
                return {
                    'active': self.ble.active() if self.ble else False,
                    'scanning': self.scanning,
                    'devices': len(self.esp_c3_devices),
                    'online': sum(1 for d in self.esp_c3_devices.values() if d['online']),
                    'memory_free': gc.mem_free(),
                    'errors': self.stats['errors_caught'],
                    'cleanups': self.stats['memory_cleanups']
                }
                
            elif cmd == "ble_set_max_devices":
                # 调整最大设备数
                max_dev = params.get('max', 50)
                if 10 <= max_dev <= 100:
                    self.max_devices = max_dev
                    return {'success': True, 'max_devices': max_dev}
                return {'success': False, 'error': '范围必须在10-100之间'}
                
            elif cmd == "ble_clear_offline":
                # 清除离线设备
                removed = 0
                for mac in list(self.esp_c3_devices.keys()):
                    if not self.esp_c3_devices[mac]['online']:
                        del self.esp_c3_devices[mac]
                        removed += 1
                
                gc.collect()
                return {'success': True, 'removed': removed, 'memory_free': gc.mem_free()}
                
            elif cmd == "ble_emergency_cleanup":
                # 紧急清理
                self.esp_c3_devices.clear()
                gc.collect()
                await self.loader.send_log('warning', '执行紧急内存清理')
                return {'success': True, 'memory_free': gc.mem_free()}
                
            elif cmd == "ble_get_stats":
                # 获取统计信息
                return {
                    'stats': self.stats,
                    'device_count': len(self.esp_c3_devices),
                    'memory': {
                        'free': gc.mem_free(),
                        'threshold': self.memory_threshold,
                        'critical': self.critical_memory
                    }
                }
                
        except Exception as e:
            self.stats['errors_caught'] += 1
            return {'success': False, 'error': str(e)}
        
        return None
    
    async def run(self):
        """插件主运行循环（轻量级）"""
        # 主要工作由异步任务处理
        await asyncio.sleep(30)
    
    async def cleanup(self):
        """清理资源"""
        print("[BLE信标] 开始清理资源...")
        self.running = False
        
        # 取消所有异步任务
        for task in [self._scan_task, self._report_task, self._cleanup_task]:
            if task:
                task.cancel()
                try:
                    await task
                except asyncio.CancelledError:
                    pass
                except Exception:
                    pass
        
        # 停止扫描
        if self.scanning:
            try:
                self.ble.gap_scan(None)
            except:
                pass
        
        # 关闭蓝牙
        if self.ble:
            try:
                self.ble.active(False)
                print("[BLE信标] 蓝牙已关闭")
            except:
                pass
        
        # 清理数据
        self.esp_c3_devices.clear()
        gc.collect()
        
        print("[BLE信标] 清理完成，释放内存: %d bytes" % gc.mem_free())

# 插件入口
Plugin = BLEBeaconScanner